截至[Day18]為止,我們的plate
及box
參數都是寫死在main
裡,今天我們來試試如何將這些參數抽取出來。
一個直覺的方式是直接將所有想要調整的變數直接抽出來,作為main
的參數。這個方法雖然可行,但是每次參數有所增刪時,都要記得修改。我們的作法是讓main
吃一個config dict
,再於main
內取出config
中的參數值。
下面除了plate
及box
的參數外,還另外抽取了初始速度及結束時間兩個參數作為示範。
#box_drop.py
def main(config):
l_p = config['l_p']
w_p = config['w_p']
en1_p = config['en1_p']
en2_p = config['en2_p']
z_elv_p = config['z_elv_p']
move_x_p = config['move_x_p']
move_y_p = config['move_y_p']
move_xy_p = (move_x_p, move_y_p)
rot_angle_p = config['rot_angle_p']
l_b = config['l_b']
w_b = config['w_b']
h_b = config['h_b']
en1_b = config['en1_b']
en2_b = config['en2_b']
en3_b = config['en3_b']
z_elv_b = config['z_elv_b']
move_x_b = config['move_x_b']
move_y_b = config['move_y_b']
move_xy_b = (move_x_b, move_y_b)
rot_angle_b = config['rot_angle_b']
vz = config['vz']
endtim = config['endtim']
initial_velocity = create_initial_velocity(
{'NSID': box_set._id, 'VZ': vz}, deck=deck)
ctrl_params = [('TERMINATION', {'ENDTIM': endtim})]
下一步是如何產生這個config dict
,除了直接用打的之外,如果能有個GUI介面也是不錯的選擇。
以下我們使用Streamlit來建立一個GUI介面。
A faster way to build and share data apps
Streamlit turns data scripts into shareable web apps in minutes.
All in pure Python. No front‑end experience required.
Streamlit
將很多需要JavaScript及CSS才能有的前端體驗打包起來,讓我們可以用純Python建立一個可以分享的app,而不需要煩惱太多前端的設定。
.streamlit
資料夾,並於其內產生config.toml
及secrets.toml
兩個檔案。config.toml
可以儲存對Streamlit
的設定,這邊我們指定使用dark
主題。secrets.toml
可以用來儲存一些敏感資訊,有點像是Python的.env
檔案。package
,Streamlit
、boto3
及pydantic
寫入requirements.txt
requirements.txt
內的package
。mkdir .streamlit
touch .streamlit/{config,secrets}.toml
echo -e "[theme]\nbase = \"dark\"" >> .streamlit/config.toml
echo -e "streamlit\nboto3\npydantic" >> requirements.txt
python3 -m venv venv
source venv/bin/activate
pip install -r requirements.txt
再來,我們建立一個st_app.py
來撰寫Streamlit
的相關程式。Streamlit
內有很多widget可以使用,我們簡單介紹幾個有使用到的。
config dict
所需要的。Number Input
可以設定名字、預設值及最大最小值等。最後我們使用了pydantic來作為寫出config
檔之前的parse工作,讓我們寫出一致的格式。其用法相當直觀,建立一個繼承BaseModel
的class
,在其中定義變數名,並指定想要的type
,即會盡可能達成parse工作。
plate
、box
及control params
建立三個pydantic model
。#st_app.py
import json
import streamlit as st
from pydantic import BaseModel
st.set_page_config('Box Drop', layout='centered')
st.header('Box Drop Config')
class PlateModel(BaseModel):
l_p: float
w_p: float
en1_p: int
en2_p: int
z_elv_p: float
move_x_p: float
move_y_p: float
rot_angle_p: float
class BoxModel(BaseModel):
l_b: float
w_b: float
h_b: float
en1_b: int
en2_b: int
en3_b: int
z_elv_b: float
move_x_b: float
move_y_b: float
rot_angle_b: float
class CRTLParamsModel(BaseModel):
vz: float
endtim: float
st.form
,於其內建立三個st.container
, 用來分類plate
、box
及control params
。每個container
中的widget會使用st.columns
加以排版。#st_app.py
def main():
with st.form('submit-form'):
plate = st.container()
box = st.container()
crtl_params = st.container()
with plate:
st.write('Plate')
col_11, col_12 = st.columns(2)
with col_11:
l_p = st.number_input('l_p(mm)', value=100.0, step=1.0)
with col_12:
w_p = st.number_input('w_p(mm)', value=100.0, step=1.0)
col_21, col_22 = st.columns(2)
with col_21:
en1_p = st.number_input('en1_p', value=10, min_value=1)
with col_22:
en2_p = st.number_input('en2_p', value=10, min_value=1)
col_31, col_32, col_33, col_34 = st.columns(4)
with col_31:
z_elv_p = st.number_input('z_elv_p', value=0.0, step=1.0)
with col_32:
move_x_p = st.number_input('move_x_p(mm)', value=0.0, step=1.0)
with col_33:
move_y_p = st.number_input('move_y_p(mm)', value=0.0, step=1.0)
with col_34:
rot_angle_p = st.number_input(
'rot_angle_p(deg)', value=0.0, min_value=0.0, max_value=360.0, step=1.0)
with box:
st.write('Box')
col_51, col_52, col_53 = st.columns(3)
with col_51:
l_b = st.number_input('l_b(mm)', value=50.0, step=1.0)
with col_52:
w_b = st.number_input('w_b(mm)', value=50.0, step=1.0)
with col_53:
h_b = st.number_input('h_b(mm)', value=50.0, step=1.0)
col_61, col_62, col_63 = st.columns(3)
with col_61:
en1_b = st.number_input('en1_b', value=10, min_value=1)
with col_62:
en2_b = st.number_input('en2_b', value=10, min_value=1)
with col_63:
en3_b = st.number_input('en3_b', value=10, min_value=1)
col_71, col_72, col_73, col_74 = st.columns(4)
with col_71:
z_elv_b = st.number_input('z_elv_b(mm)', value=5.0, step=1.0)
with col_72:
move_x_b = st.number_input(
'move_x_b(mm)', value=50.0, step=1.0)
with col_73:
move_y_b = st.number_input(
'move_y_b(mm)', value=20.0, step=1.0)
with col_74:
rot_angle_b = st.number_input(
'rot_angle_b(deg)', value=45.0, min_value=0.0, max_value=360.0, step=1.0)
with crtl_params:
st.write('Control params')
col_101, col_102 = st.columns(2)
with col_101:
vz = st.number_input('vz(mm/s)', value=-500.0, step=1.0)
with col_102:
endtim = st.number_input(
'end_time(s)', value=1.5E-2, min_value=0.0)
submit_button = st.form_submit_button('Submit')
submit button
後,我們將plate
、box
及control params
分別丟入相對應的pydantic model
進行parse後,並利用pydantic
的dict function
將其裝入一個名為data
的dict
後,將其寫出一個名為input_data.json
的json
檔案於本機端。#st_app.py
if submit_button:
plate_model = PlateModel(**{'l_p': l_p,
'w_p': w_p,
'en1_p': en1_p,
'en2_p': en2_p,
'z_elv_p': z_elv_p,
'move_x_p': move_x_p,
'move_y_p': move_y_p,
'rot_angle_p': rot_angle_p})
box_model = BoxModel(**{'l_b': l_b,
'w_b': w_b,
'h_b': h_b,
'en1_b': en1_b,
'en2_b': en2_b,
'en3_b': en3_b,
'z_elv_b': z_elv_b,
'move_x_b': move_x_b,
'move_y_b': move_y_b,
'rot_angle_b': rot_angle_b})
crtl_params_model = CRTLParamsModel(**{'vz': vz, 'endtim': endtim})
data = {**plate_model.dict(),
**box_model.dict(),
**crtl_params_model.dict()}
filename = 'input_data.json'
with open(filename, 'w') as f:
json.dump(data, f)
now = datetime.now().strftime('%Y%m%d_%H%M%s')
st.info(f'{filename} is written at {now}.')
config
檔並傳給main
,即可透過ANSA建立模型並求解完畢。#box_drop.py
if __name__ == '__main__':
jfile = Path.cwd() / 'input_data.json'
with open(jfile) as f:
config = json.load(f)
main(config)
terminal
中輸入streamlit run st_app.py
開啟這個App
。